本篇文章會修改一些 LighsPlugin 定義的功能,並且測試使用 4o 跟 4o-mini 在解析語意跟執行上的效果
考慮到除了已經定義好的燈,有時候也會想要在新增更多燈來控制,所以這次在plugin中加入了定義增加設備的功能,除此之外也新增了一項如果不是在控制燈,那就將內容送給 Chat GPT 將分析的結果傳回
關於設定的部分要特別注意 ==@KernelFunctionParameter==,這個部分當 AI 辨識語意決定使用的 function 的時候,如果有標註這個參數的註解,則會由 AI 辨識相關的參數,輸入到這個 function 進行使用,比如當我要開關燈時,最好的寫法就是指定燈的 id=? 然後指定 isOn=?,像這樣標示清楚參數的話,在辨識上除了指令執行比較準確外也判斷的比較快
相反的若是沒有指定清楚就要靠 AI 模型本身的理解能力了
另外參數的型態也會影響解析的效果,因此這個部分要特別注意
public class LightsPlugin {
// Mock data for the lights
private final Map<Integer, LightModel> lights = new HashMap<>();
public LightsPlugin() {
lights.put(1, new LightModel(1, "Table Lamp", false));
lights.put(2, new LightModel(2, "Porch light", false));
lights.put(3, new LightModel(3, "Chandelier", true));
}
@DefineKernelFunction(name = "get_lights", description = "Gets a list of lights and their current state")
public String getLights() {
System.out.println("Getting lights");
return new ArrayList<>(lights.values()).toString();
}
@DefineKernelFunction(name = "change_state", description = "Changes the state of the light")
public String changeState(
@KernelFunctionParameter(name = "id", description = "The ID of the light to change") int id,
@KernelFunctionParameter(name = "isOn", description = "The new state of the light") boolean isOn) {
System.out.println("Changing light " + id + " " + isOn);
if (!lights.containsKey(id)) {
throw new IllegalArgumentException("Light not found");
}
lights.get(id).setOn(isOn);
return lights.get(id).toString();
}
@DefineKernelFunction(name = "add_new_light", description = "Adds a new light to the list")
public String addNewLight(
@KernelFunctionParameter(name = "id", description = "The ID of the light to add") int id,
@KernelFunctionParameter(name = "name", description = "The name of the light to add") String name,
@KernelFunctionParameter(name = "isOn", description = "The state of the light to add") boolean isOn) {
System.out.println("Adding new light " + id + " " + name + " " + isOn);
if (lights.containsKey(id)) {
throw new IllegalArgumentException("Light already exists");
}
lights.put(id, new LightModel(id, name, isOn));
return lights.get(id).toString();
}
@DefineKernelFunction(name = "to_chat_gpt", description = "If there is not talking about light then it will be sent to the GPT model")
public String toChatGPT(
@KernelFunctionParameter(name = "input", description = "The input to send to the GPT model") String input) {
System.out.println("Sending to GPT: " + input);
return "I'm sorry, I don't understand that.";
}
}
這次額外新增一個路徑專門處理這個 LightsPlugin 相關的語意功能
@GetMapping("/lights")
public ResponseEntity<Object> getLights(@RequestBody LightsPluginRequest request) {
return ResponseEntity.ok(service.getLights(request));
}
新增一個 getLights 的功能,在這裡可以看到我們在一開始使用時先設定的初始狀態,這個部分是因為在實測中發現 AI 沒辦法準確的獲取有什麼狀態,因此會導致在使用時還要先講述一次設定才會將初始設定記住,為了避免這個問題就在一開始設定了
ChatHistory history = new ChatHistory();
public Object getLights(LightsPluginRequest request) {
if (history.getMessages().size() == 0){
history.addUserMessage("light 1 是桌燈 狀態是關閉、light 2 是檯燈 狀態是關閉、light 3 是吊燈 狀態是開啟");
}
history.addUserMessage(request.input());
List<ChatMessageContent<?>> results = chatCompletionService.getChatMessageContentsAsync(
history,
kernel,
invocationContext
).block();
for (ChatMessageContent<?> result : results) {
if (!result.getContent().contains("error"))
history.addMessage(result);
}
System.out.println(results.get(0).getContent());
return results.get(0).getContent();
}
我們會以同樣的句子下去測試,查看雙方在接收指令、辨識的效果差在哪
現在所有燈的狀態是什麼 (成功)
幫我開啟檯燈 (成功)
幫我開啟 id=1 的桌燈 isOn=true (成功)
新增 Light 4 id=4 name=客廳的燈 isOn=關閉 (失敗)
也是有例外可能出現,即使寫得很清楚但是執行上就是有辨識失敗的可能
新增 Light 4 id=4 name=客廳的燈 isOn=false (成功)
相同意思的指令,兩兩對比下可以發現跟參數定義的型態一樣的更容易辨識成功
未來生成式 AI 可以結合並運用的情境有什麼
現在所有燈的狀態是什麼 (成功)
幫我開啟檯燈 (失敗)
幫我開啟 id=2 的檯燈 isOn=開啟 (成功)
幫我開啟 id=1 的桌燈 isOn=true (成功)
當我將參數指定好,並且也照著參數的型態去設定的話,回復的準確度就比較高
新增 Light 4 id=4 name=客廳的燈 isOn=關閉 (失敗)
新增 Light 4 id=4 name=客廳的燈 isOn=false (成功)
未來生成式 AI 可以結合並運用的情境有什麼 (成功)
gpt-4o 在分析語意以及執行指令上表現更加出色,4o 不用將問題寫得很清楚還是有辦法理解並執行任務,回話的內容也比較完整
gpt-4o-mini 在分析語意即執行指令上需要更加完整的指令才可以有效提高準確度
兩者共同的點都是如果參數名稱、型態都是按照 plugin 指定的參數輸入時,回覆的速度跟準確度都較高